﻿using System;
using System.Collections.Generic;
using System.Text;
using EmbeddedSharp.Core;
using Zove.Runtime.Core.Zoom;

namespace Zove.Runtime.Core {
    /// <summary>
    /// The runtime factor status
    /// </summary>
    public enum RuntimeFactorStatus {
        Input = 0,
        Solved,
        Evaluated,
        Unknown
    }

    /// <summary>
    /// Runtime factor
    /// </summary>
    public class RuntimeFactor {
        /// <summary>
        /// Status
        /// </summary>
        protected RuntimeFactorStatus status = RuntimeFactorStatus.Unknown;

        /// <summary>
        /// Value
        /// </summary>
        protected double value;

        /// <summary>
        /// Default constructor
        /// </summary>
        public RuntimeFactor() {
            Status = RuntimeFactorStatus.Unknown;
            Equations = new List<RuntimeEquation>();
            Rules = new List<RuntimeRule>();
        }

        /// <summary>
        /// Constructor with a name
        /// </summary>
        /// <param name="factorName"></param>
        public RuntimeFactor(string factorName)
            : this() {
            Name = factorName;
        }

        /// <summary>
        /// The name
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// The unit
        /// </summary>
        public string Unit { get; set; }

        /// <summary>
        /// Full name
        /// </summary>
        public string QualifiedName {
            get {
                return string.IsNullOrEmpty(this.Model.Instance.Name) ? Name : this.Model.Instance.Name + "." + Name;
            }
        }

        /// <summary>
        /// The rule to solved this factor
        /// </summary>
        public RuntimeRule SolvedRule { get; set; }

        /// <summary>
        /// The value
        /// </summary>
        public double Value {
            get {
                Evaluate();
                return value;
            }
            set { this.value = value; }
        }

        /// <summary>
        /// The status
        /// </summary>
        public RuntimeFactorStatus Status {
            get {
                Evaluate();
                return status;
            }
            private set {
                status = value;

                if (value == RuntimeFactorStatus.Input) {
                    if (Model != null) {
                        Model.WasUpdated = true;
                    }
                }
            }
        }

        /// <summary>
        /// The equations associated with this factor
        /// </summary>
        public List<RuntimeEquation> Equations { get; set; }

        /// <summary>
        /// The rules from the equations
        /// </summary>
        public List<RuntimeRule> Rules { get; set; }

        /// <summary>
        /// The model to where the factor belongs
        /// </summary>
        public RuntimeModel Model { get; set; }

        /// <summary>
        /// Evaluate the factor
        /// </summary>
        private void Evaluate() {
            if (Model == null) {
                return;
            }

            if (Model.WasUpdated) {
                Model.WasUpdated = false;
                Model.Instance.Evaluate();
            }
        }

        /// <summary>
        /// Initialize the factor.
        /// </summary>
        /// <param name="initValue">the value of the factor</param>
        public void Initialize(double initValue) {
            Value = initValue;
            Status = RuntimeFactorStatus.Input;
        }

        /// <summary>
        /// Mark the factor has been solved with a given RuntimeRule.
        /// </summary>
        /// <param name="rr">the solved rule</param>
        public void Solve(RuntimeRule rr) {
            SolvedRule = rr;
            if (rr.Root != null) {
                object ret = rr.Root.Evaluate();
                double dret = Convert.ToDouble(ret);
                Value = dret;
            }
            Status = RuntimeFactorStatus.Solved;
        }

        /// <summary>
        /// Set the status to be unknown.
        /// </summary>
        public void Reset() {
            Status = RuntimeFactorStatus.Unknown;
        }

        /// <summary>
        /// Formulate the evaluating expression of the factor
        /// </summary>
        /// <returns></returns>
        public string Formulate() {
            var ss = new SolutionFormulator();
            string result = ss.Formulate(this);

            return result;
        }

        /// <summary>
        /// Check if the factor could generate a solution
        /// </summary>
        public bool IsFormulatable {
            get {
                return (Status == RuntimeFactorStatus.Input || Status == RuntimeFactorStatus.Solved) &&
                SolvedRule != null && SolvedRule.Root != null;
            }
        }

        /// <summary>
        /// The name of the factor
        /// </summary>
        /// <returns></returns>
        public override string ToString() {
            return string.Format("{0} = {1}", Name, Status == RuntimeFactorStatus.Unknown ? "<n/a>" : Value.ToString());
        }
    }

    /// <summary>
    /// Runtime equation
    /// </summary>
    public class RuntimeEquation {
        /// <summary>
        /// Default constructor
        /// </summary>
        public RuntimeEquation() {
            Factors = new List<RuntimeFactor>();
        }

        /// <summary>
        /// Constructor with an equation
        /// </summary>
        /// <param name="equation"></param>
        public RuntimeEquation(string equation)
            : this() {
            Equation = equation;
        }

        /// <summary>
        /// The left hand side expression tree
        /// </summary>
        public Node Left { get; set; }

        /// <summary>
        /// The right hand side expression tree
        /// </summary>
        public Node Right { get; set; }

        /// <summary>
        /// The equation
        /// </summary>
        public string Equation { get; set; }

        /// <summary>
        /// Factors associated in this equation
        /// </summary>
        public List<RuntimeFactor> Factors { get; set; }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override string ToString() {
            return Equation;
        }
    }

    /// <summary>
    /// Runtime rule
    /// </summary>
    public class RuntimeRule {
        /// <summary>
        /// The equation
        /// </summary>
        public RuntimeEquation Equation { get; set; }

        /// <summary>
        /// The factor to evaluate
        /// </summary>
        public RuntimeFactor Factor { get; set; }

        /// <summary>
        /// The expression tree
        /// </summary>
        public Node Root { get; set; }

        public override string ToString() {
            return Root.ToString();
        }
    }

    /// <summary>
    /// Runtime model
    /// </summary>
    public class RuntimeModel {
        /// <summary>
        /// Default constructor
        /// </summary>
        public RuntimeModel() {
            WasUpdated = false;
            Factors = new RuntimeFactorCollection();
            Equations = new List<RuntimeEquation>();
            Rules = new Dictionary<string, RuntimeRule>();

            AssociatedClasses = new List<string>();
        }

        /// <summary>
        /// get the number of available factors
        /// </summary>
        public int NumberOfAvailableFactors {
            get {
                int c = 0;

                foreach (RuntimeFactor rf in Factors) {
                    if (rf.Status != RuntimeFactorStatus.Unknown) {
                        c++;
                    }
                }

                return c;
            }
        }

        public bool WasUpdated { get; internal set; }

        /// <summary>
        /// get the name of the model
        /// </summary>
        public string Name { get; set; }

        public List<string> AssociatedClasses { get; set; }

        /// <summary>
        /// The factors
        /// </summary>
        public RuntimeFactorCollection Factors { get; private set; }

        /// <summary>
        /// The equations
        /// </summary>
        public List<RuntimeEquation> Equations { get; private set; }

        /// <summary>
        /// Rules with keys of the factors
        /// </summary>
        public Dictionary<string, RuntimeRule> Rules { get; set; }

        /// <summary>
        /// The instance
        /// </summary>
        public ZoveObject Instance { get; set; }

        /// <summary>
        /// Evaluate the expression
        /// </summary>
        /// <param name="expr">expression</param>
        /// <returns>result</returns>
        public object Evaluate(string expr) {
            var e = new ZoomExpression(expr, this);

            return e.Evaluate();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override string ToString() {
            var sb = new StringBuilder();

            return sb.ToString();
        }

        /// <summary>
        /// Get a factor by its name
        /// </summary>
        /// <param name="factorName">the factor name</param>
        /// <returns>the factor, null if not found</returns>
        public RuntimeFactor GetFactorByName(string factorName) {
            foreach (RuntimeFactor rf in Factors) {
                if (rf.Name == factorName) {
                    return rf;
                }
            }

            return null;
        }

        /// <summary>
        /// Get the rule id for the given RuntimeEquation
        /// </summary>
        /// <param name="re">the runtime equation</param>
        /// <param name="rf">the runtime factor</param>
        /// <returns>the rule id</returns>
        public static string GetRuleId(RuntimeEquation re, RuntimeFactor rf) {
            return re.GetHashCode() + "|" + rf.GetHashCode();
        }

        /// <summary>
        /// Get runtime rule
        /// </summary>
        /// <param name="re">the runtime equation</param>
        /// <param name="rf">runtime factor</param>
        /// <returns>runtime rule</returns>
        public RuntimeRule GetRuntimeRule(RuntimeEquation re, RuntimeFactor rf) {
            string rid = GetRuleId(re, rf);

            return Rules[rid];
        }

        /// <summary>
        /// Combine another runtime model
        /// </summary>
        /// <param name="rm">runtime model</param>
        public void Add(RuntimeModel rm) {
            Equations.AddRange(rm.Equations);

            foreach (RuntimeFactor rf in rm.Factors) {
                RuntimeFactor rff = Factors[rf.Name];
                if (rff != null) {
                    rff.Equations.AddRange(rf.Equations);
                    rff.Rules.AddRange(rf.Rules);
                } else {
                    //this.Factors.Add(rf);
                    AddFactor(rf);
                }
            }

            foreach (string key in rm.Rules.Keys) {
                RuntimeRule rr = rm.Rules[key];
                if (Rules.ContainsKey(key)) {
                    throw new DuplicatedRuleDefinedException(rr);
                }

                Rules[key] = rr;
            }
        }

        /// <summary>
        /// Add a new factor
        /// </summary>
        /// <param name="factorName">the name of the factor</param>
        /// <returns>the resultant factor</returns>
        public RuntimeFactor AddFactor(string factorName) {
            var rf = new RuntimeFactor { Name = factorName };

            return AddFactor(rf);
        }

        /// <summary>
        /// Add a factor to the instance
        /// </summary>
        /// <param name="rf">the runtime factor</param>
        /// <returns>the runtime factor</returns>
        public RuntimeFactor AddFactor(RuntimeFactor rf) {
            rf.Model = this;
            Factors.Add(rf);

            return rf;
        }
    }
}